﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Async;
using System.Web.Security;
using CodeStash.Common.DataAccess.EntityFramework;
using CodeStash.Common.DataAccess.Repository;
using CodeStash.Common.DataAccess.UnitOfWork;
using CodeStash.Common.Enums;
using CodeStash.Filters;
using CodeStash.Models.Search;
using CodeStash.Services;
using CodeStash.Utils;
using Telerik.Web.Mvc;
using System.Text;
using CodeStash.Models.Snippet;

namespace CodeStash.Controllers
{

    public class SearchTaskState
    {
        public ViewDataDictionary ViewData { get; private set; }
        public CreateSearchViewModel Vm { get; private set; }
        public Guid UserId { get; private set; }


        public SearchTaskState(
            ViewDataDictionary viewData,
            CreateSearchViewModel vm,
            Guid userId)
        {
            if (userId == Guid.Empty)
            {
                // If there's no user logged in, the search defaults to Public visibility
                // as there's no concept of JustMe or Team for an unauthenticated user.
                vm.Visibility = (int)CodeSnippetVisibility.Public;
            }
            this.ViewData = viewData;
            this.Vm = vm;
            this.UserId = userId;
        }


    }



    public class SearchController : BaseTagCloudEnabledAsyncController
    {
        private readonly ILoggerService loggerService;
        private readonly IMembershipService membershipService;
        private readonly IRepository<Language> languageRepository;
        private readonly IRepository<Visibility> visibilityRepository;
        private readonly IRepository<CodeCategory> categoryRepository;
        private readonly IRepository<Grouping> groupingRepository;
        private readonly IRepository<CodeSnippet> codeSnippetRepository;
        private readonly IRepository<CodeTag> codeTagRepository;
        private readonly IRepository<CreatedTeam> createdTeamRepository;
        private readonly IUnitOfWork unitOfWork;



        public SearchController(
            ILoggerService loggerService,
            IMembershipService membershipService,
            ITagCloudService tagCloudService,
            IRepository<Language> languageRepository,
            IRepository<Visibility> visibilityRepository,
            IRepository<CodeCategory> categoryRepository,
            IRepository<Grouping> groupingRepository,
            IRepository<CodeSnippet> codeSnippetRepository,
            IRepository<CodeTag> codeTagRepository,
            IRepository<CreatedTeam> createdTeamRepository,
            IUnitOfWork unitOfWork)
            : base(tagCloudService)
        {
            this.loggerService = loggerService;
            this.membershipService = membershipService;
            this.languageRepository = languageRepository;
            this.visibilityRepository = visibilityRepository;
            this.categoryRepository = categoryRepository;
            this.groupingRepository = groupingRepository;
            this.codeSnippetRepository = codeSnippetRepository;
            this.codeTagRepository = codeTagRepository;
            this.createdTeamRepository = createdTeamRepository;
            this.unitOfWork = unitOfWork;
        }





        //[Authorize]
        [RenderTagCloud]
        [HttpGet]
        public ActionResult CreateSearch()
        {
            loggerService.Info("Search : Creating new search ViewModel");

            CreateSearchViewModel vm = new CreateSearchViewModel();
            using (unitOfWork)
            {
                RefreshModelStaticData(vm);
            }
            return View(vm);
        }


        //[Authorize]
        [RenderTagCloud]
        [HttpPost]
        [ValidateAntiForgeryToken(Salt = "CreateSearch")]
        public void CreateSearchAsync(CreateSearchViewModel vm)
        {
            AsyncManager.OutstandingOperations.Increment();

            if (ModelState.IsValid)
            {

                ViewData["successfulCreate"] = true;
                try
                {
                    MembershipUser user = membershipService.GetUserByUserName(User.Identity.Name);
                    Guid userId = Guid.Empty;
                    if (user != null) userId = Guid.Parse(user.ProviderUserKey.ToString());
                    SearchTaskState searchTaskState = new SearchTaskState(ViewData, vm, userId);

                    loggerService.Info(string.Format("Search : Creating background Task for Search. Where search specified was : {0}",
                        GetSearchDescriptionDetails(vm)));

                    Task<ShowSearchResultsViewModel> searchTask = CreateSearchTask(searchTaskState);
                    searchTask.Start();
                    searchTask.ContinueWith((ant) =>
                    {
                        loggerService.Info(string.Format("Search : Ran to completion and found {0} matched snippets",
                            ant.Result.CodeSnippets.Count()));
                        SetValidSearchAsyncResult(AsyncManager, ant.Result);
                        AsyncManager.OutstandingOperations.Decrement();
                    }, TaskContinuationOptions.OnlyOnRanToCompletion);

                    searchTask.ContinueWith((ant) =>
                    {
                        loggerService.Error("Search : Didn't run to completion");
                        SetInvalidAsyncResult(AsyncManager, vm);
                        AsyncManager.OutstandingOperations.Decrement();
                    }, TaskContinuationOptions.OnlyOnFaulted);

                }
                catch (AggregateException ex)
                {
                    AggregateException flattened = ex.Flatten();
                    loggerService.Error(string.Format("Search : AggregateException found\r\nSearch was : {0}\r\n{1}",
                        flattened.Message, flattened.StackTrace));
                    SetInvalidAsyncResult(AsyncManager, vm);
                    AsyncManager.OutstandingOperations.Decrement();
                }
            }
            else
            {
                string errors = ModelState.Keys.Aggregate((x, y) => string.Format("{0}\r\n{1}", x, y));
                loggerService.Error(string.Format("Search : CreateSearchAsync model is not valid\r\nError keys were\r\n{0}\r\n\r\nSearch was : {1}",
                    errors, GetSearchDescriptionDetails(vm)));
                SetInvalidAsyncResult(AsyncManager, vm);
                AsyncManager.OutstandingOperations.Decrement();
            }

        }

        public ActionResult CreateSearchCompleted(bool wasValid, object vm)
        {
            if (!wasValid)
            {
                loggerService.Error("Search : CreateSearch Completed but was not valid");
                return View("CreateSearch", vm);
            }
            else
            {
                loggerService.Info("Search : CreateSearch Completed with no errors");
                return View("ShowSearchResults", vm);
            }
        }



        [GridAction]
        public ActionResult _AjaxBinding()
        {
            return View(new GridModel((IEnumerable)Session["searchResults"]));
        }



        //[Authorize]
        [HttpPost]
        [AjaxOnly]
        public JsonResult GetSpecificSnippetData(int snippetId)
        {
            using (unitOfWork)
            {
                codeSnippetRepository.EnrolInUnitOfWork(unitOfWork);
                languageRepository.EnrolInUnitOfWork(unitOfWork);

                CodeSnippet snippet = codeSnippetRepository.FindBy(x => x.CodeSnippetId == snippetId).SingleOrDefault();
                Language language = languageRepository.FindBy(x => x.LanguageId == snippet.LanguageId).SingleOrDefault();
                string highlightingCSSName = CodeSnippetUtils.GetUsersSavedHighlightingCSS(User.Identity);

                if (snippet != null)
                {
                    loggerService.Error(string.Format("Search : Found matching snippet data for snippet Id '{0}'", snippetId));
                    return Json(new
                        {
                            CodeSnippetCode = snippet.ActualCode.Trim(),
                            HighlightingCSSName = highlightingCSSName,
                            PreClass = WebSiteUtils.GetCodeClass(language.LanguageCode),
                            Success = true
                        });
                }
                else
                {
                    loggerService.Error(string.Format("Search : Could not obtain any snippet data for snippet Id '{0}'", snippetId));

                    return Json(new
                    {
                        CodeSnippetCode = "",
                        HighlightingCSSName = "",
                        PreClass = "",
                        Success = false
                    });
                }

            }
        }

        private void SetInvalidAsyncResult(AsyncManager asyncManager, CreateSearchViewModel vm)
        {
            ViewData["successfulCreate"] = false;
            using (unitOfWork)
            {
                RefreshModelStaticData(vm);
            }
            asyncManager.Parameters["wasValid"] = false;
            asyncManager.Parameters["vm"] = vm;
        }

        private void SetValidSearchAsyncResult(AsyncManager asyncManager, ShowSearchResultsViewModel vm)
        {
            AsyncManager.Parameters["wasValid"] = true;
            AsyncManager.Parameters["vm"] = vm;
        }

        private Task<ShowSearchResultsViewModel> CreateSearchTask(SearchTaskState searchTaskState)
        {
            return new Task<ShowSearchResultsViewModel>((state) =>
                {
                    SearchTaskState taskState = (SearchTaskState)state;
                    ShowSearchResultsViewModel searchResultsVm;

                    using (unitOfWork)
                    {
                        createdTeamRepository.EnrolInUnitOfWork(unitOfWork);
                        codeTagRepository.EnrolInUnitOfWork(unitOfWork);
                        languageRepository.EnrolInUnitOfWork(unitOfWork);
                        codeSnippetRepository.EnrolInUnitOfWork(unitOfWork);
                        visibilityRepository.EnrolInUnitOfWork(unitOfWork);
                        categoryRepository.EnrolInUnitOfWork(unitOfWork);
                        groupingRepository.EnrolInUnitOfWork(unitOfWork);

                        String searchValue = GetSearchValueBasedOnSearchType(taskState.Vm);
                        CodeSnippetVisibility visibility = (CodeSnippetVisibility)taskState.Vm.Visibility;
                        string[] tags = new string[0];
                        if (taskState.Vm.SearchType == SearchType.ByTag)
                        {
                            tags = searchValue.Split(new char[] { ';' });
                        }

                        Tuple<int, List<CodeSnippet>> results =
                            SearchUtils.FilterByVisibility(
                                unitOfWork,
                                createdTeamRepository,
                                codeTagRepository,
                                languageRepository,
                                codeSnippetRepository,
                                taskState.Vm.SearchType,
                                searchValue.ToLower(),
                                1,
                                1,
                                false,
                                visibility,
                                taskState.UserId,
                                tags,
                                taskState.Vm.IncludeRating,
                                taskState.Vm.Rating);

                        Session["searchResults"] = results.Item2;

                        int pageSize = DisplaySnippetsViewModel.MAX_SNIPPETS_TO_DISPLAY;
                        searchResultsVm =
                            new ShowSearchResultsViewModel(
                                taskState.Vm.SearchType,
                                languageRepository.FindBy(x => x.LanguageId == taskState.Vm.LanguageId).Single(),
                                visibilityRepository.FindBy(x => x.Id == taskState.Vm.Visibility).Single(),
                                searchValue,
                                results.Item1,
                                results.Item2/*.Skip(taskState.Vm.CurrentPage - 1 * pageSize).Take(pageSize).ToList()*/);
                    }
                    return searchResultsVm;
                }, searchTaskState);


        }

        private string GetSearchValueBasedOnSearchType(CreateSearchViewModel vm)
        {
            string searchValue = "";
            switch (vm.SearchType)
            {
                case SearchType.ByKeyWord:
                    searchValue = vm.SearchForKeyWord;
                    break;
                case SearchType.ByTag:
                    searchValue = vm.SearchForTag;
                    break;
                case SearchType.ByLanguage:
                    languageRepository.EnrolInUnitOfWork(unitOfWork);
                    searchValue = languageRepository.FindBy(x => x.LanguageId == vm.LanguageId).Single().Language1;
                    break;
            }
            return searchValue;
        }

        private void RefreshModelStaticData(CreateSearchViewModel vm)
        {
            languageRepository.EnrolInUnitOfWork(unitOfWork);
            visibilityRepository.EnrolInUnitOfWork(unitOfWork);
            vm.LanguageList = languageRepository.FindAll().ToList();
            vm.VisibilityList = visibilityRepository.FindAll().ToList();
        }

        private string GetSearchDescriptionDetails(CreateSearchViewModel vm)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Search type : '{0}', ", vm.SearchType);
            sb.AppendFormat("tag value '{0}', ", vm.SearchForTag);
            sb.AppendFormat("keyword value '{0}', ", vm.SearchForKeyWord);
            sb.AppendFormat("language Id value '{0}', ", vm.LanguageId);
            sb.AppendFormat("visibility value '{0}'", vm.Visibility);
            return sb.ToString();

        }

    }
}
